package dian.qing.li.demo.security.utils;

import com.pactera.redis.utils.Md5Utils;
import com.pactera.redis.utils.RedisUtils;
import dian.qing.li.demo.security.constants.Constants;
import dian.qing.li.demo.security.constants.RedisKey;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.experimental.UtilityClass;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author: liqingdian
 **/
@UtilityClass
public class JwtUtils {

    private static final RequestHeaderRequestMatcher AUTHORIZATION_MATCHER = new RequestHeaderRequestMatcher("Authorization");
    private static final SignatureAlgorithm HS256 = SignatureAlgorithm.HS256;
    private static final long EXPIRATION = 1 * 60 * 60 * 1000L;

    /**
     * 密钥
     *
     * @param password 密码
     * @author: liqingdian
     */
    private static String secret(String password) {
        if (StringUtils.isBlank(password)) {
            password = Constants.JWT_SECRET;
        }
        return Md5Utils.md5(password);
    }

    /**
     * 生成TOKEN
     *
     * @param claims     载荷声明
     * @param secret     密钥
     * @param expiration 过期时间(毫秒)
     * @author: liqingdian
     */
    public static String token(Map<String, Object> claims, String secret, long expiration) {
        Date exp = new Date(System.currentTimeMillis() + expiration);
        String token = Jwts.builder().setClaims(claims).setExpiration(exp).signWith(HS256, JwtUtils.secret(secret)).compact();
        return token;
    }

    /**
     * 生成TOKEN
     *
     * @param userDetails 用户详情
     * @author: liqingdian
     */
    public static String token(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>(2);
        claims.put(Claims.SUBJECT, userDetails.getUsername());
        claims.put(Claims.ISSUED_AT, new Date());
        Collection<? extends GrantedAuthority> authList = userDetails.getAuthorities();
        Set<String> authorities = authList.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet());
        claims.put("authorities", authorities);
        String token = JwtUtils.token(claims, Constants.JWT_SECRET, EXPIRATION);
        String redisKey = RedisUtils.redisKey(RedisKey.JWT, userDetails.getUsername(), token);
        RedisUtils.put(redisKey, token);
        return token;
    }

    /**
     * 解析token
     *
     * @param token  TOKEN令牌
     * @param secret 密钥
     * @author: liqingdian
     */
    public static Claims claims(String token, String secret) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(JwtUtils.secret(secret)).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    /**
     * 解析用户账号
     *
     * @param token  TOKEN令牌
     * @param secret 密钥
     * @author: liqingdian
     */
    public static String username(String token, String secret) {
        String username;
        try {
            Claims claims = JwtUtils.claims(token, secret);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * 判断令牌是否过期
     *
     * @param token 令牌
     * @author: liqingdian
     */
    public static boolean isExpired(String token, String secret) {
        try {
            Claims claims = JwtUtils.claims(token, secret);
            Date expiration = claims.getExpiration();
            return expiration.before(new Date());
        } catch (Exception e) {
            return true;
        }
    }

    /**
     * 获取token
     *
     * @param request 请求
     * @author: liqingdian
     */
    public static String getJwtToken(HttpServletRequest request) {
        String token = "";
        if (AUTHORIZATION_MATCHER.matches(request)) {
            token = StringUtils.removeStart(request.getHeader("Authorization"), "Bearer ");
        }
        return token;
    }

    /**
     * 刷新令牌
     *
     * @param token 原令牌
     * @author: liqingdian
     */
    public static String refreshToken(String token, String secret) {
        String refreshedToken;
        try {
            Claims claims = JwtUtils.claims(token, secret);
            claims.put(Claims.ISSUED_AT, new Date());
            refreshedToken = JwtUtils.token(claims, secret, EXPIRATION);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    /**
     * 验证令牌
     *
     * @param token       令牌
     * @param userDetails 用户
     * @author: liqingdian
     */
    public static boolean verifyToken(String token, UserDetails userDetails) {
        String username = JwtUtils.username(token, Constants.JWT_SECRET);
        String redisKey = RedisUtils.redisKey(RedisKey.JWT, userDetails.getUsername(), token);
        boolean hasKey = RedisUtils.hasKey(redisKey);
        boolean verity = hasKey && username.equals(userDetails.getUsername()) && !JwtUtils.isExpired(token, Constants.JWT_SECRET);
        if (verity) {
            RedisUtils.expire(redisKey);
        } else if (hasKey) {
            RedisUtils.delete(redisKey);
        }
        return verity;
    }

    /**
     * 注销TOKEN
     *
     * @param request 请求
     * @author: liqingdian
     */
    public static void cancel(HttpServletRequest request) {
        String token = JwtUtils.getJwtToken(request);
        if (StringUtils.isNotBlank(token)) {
            String username = JwtUtils.username(token, Constants.JWT_SECRET);
            if (StringUtils.isNotBlank(username)) {
                String redisKey = RedisUtils.redisKey(RedisKey.JWT, username, token);
                RedisUtils.delete(redisKey);
            }
        }
    }

    /**
     * 刷新TOKEN
     *
     * @param token token
     * @author: liqingdian
     */
    public static void refresh(String token) {
        if (StringUtils.isNotBlank(token)) {
            String username = JwtUtils.username(token, Constants.JWT_SECRET);
            if (StringUtils.isNotBlank(username)) {
                String redisKey = RedisUtils.redisKey(RedisKey.JWT, username, token);
                RedisUtils.put(redisKey, token);
            }
        }
    }
}
